//----------------------------------------------------------------------------
// Anti-Grain Geometry - Version 2.4
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
//
// C# Port port by: Lars Brubaker
//                  larsbrubaker@gmail.com
// Copyright (C) 2007
//
// Permission to copy, use, modify, sell and distribute this software 
// is granted provided this copyright notice appears in all copies. 
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
//
//----------------------------------------------------------------------------
// Contact: mcseem@antigrain.com
//          mcseemagg@yahoo.com
//          http://www.antigrain.com
//----------------------------------------------------------------------------
//
// class platform_support
//
//----------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

using AGG3D.Graphics;
using AGG.Image;
using AGG.RasterizerScanline;

using Tao.OpenGl;
using Tao.Platform.Windows;

namespace AGG.UI
{
    public class WindowsFormsOpenGLBackendGuiFactory : IGuiFactory
    {
        public string GetFactoryName()
        {
            return "WindowsFormsOpenGLBackendGuiFactory";
        }

        public GuiHalSurface CreateSurface(int Width, int Height, GuiHalSurface.CreateFlags flags, GuiHalSurface.PixelFormat pixelFormat)
        {
            GuiHalSurface newSurface;

            switch (pixelFormat)
            {
                case GuiHalSurface.PixelFormat.PixelFormatBgra32:
                    newSurface = new GuiHalSurfaceWindowsFormsOpenGLBackend(GuiHalSurface.ImageFormats.pix_format_bgra32);
                    break;
                default:
                    throw new NotImplementedException();
            }

            newSurface.Init(Width, Height, flags, pixelFormat);
            return newSurface;
        }
    }

    public class WindowsFormBridgeOpenGLBackend : AbstractWindowsFormBridge
    {
        protected static IntPtr hDC;                                              // Private GDI Device Context
        private static IntPtr hRC;                                              // Permanent Rendering Context

        public WindowsFormBridgeOpenGLBackend(GuiHalSurface app, GuiHalSurface.ImageFormats format)
            : base(app, format)
        {
            this.CreateParams.ClassStyle = this.CreateParams.ClassStyle |       // Redraw On Size, And Own DC For Window.
                User.CS_HREDRAW | User.CS_VREDRAW | User.CS_OWNDC;
            //this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);            // No Need To Erase Form Background
            this.SetStyle(ControlStyles.DoubleBuffer, true);                    // Buffer Control
            this.SetStyle(ControlStyles.Opaque, true);                          // No Need To Draw Form Background
            this.SetStyle(ControlStyles.ResizeRedraw, true);                    // Redraw On Resize
            this.SetStyle(ControlStyles.UserPaint, true);                       // We'll Handle Painting Ourselves

            SetUpFormsWindow(app, format);
        }

        internal virtual bool CreateGLWindow(int width, int height, int bitsPerPixel, bool fullscreenflag)
        {
            int pixelFormat;                                                    // Holds The Results After Searching For A Match
            fullscreen = fullscreenflag;                                        // Set The Global Fullscreen Flag

            GC.Collect();                                                       // Request A Collection
            // This Forces A Swap
            Kernel.SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, -1, -1);

            if (fullscreen)
            {
                Gdi.DEVMODE dmScreenSettings = new Gdi.DEVMODE();               // Device Mode
                // Size Of The Devmode Structure
                dmScreenSettings.dmSize = (short)Marshal.SizeOf(dmScreenSettings);
                dmScreenSettings.dmPelsWidth = width;                           // Selected Screen Width
                dmScreenSettings.dmPelsHeight = height;                         // Selected Screen Height
                dmScreenSettings.dmBitsPerPel = bitsPerPixel;                           // Selected Bits Per Pixel
                dmScreenSettings.dmFields = Gdi.DM_BITSPERPEL | Gdi.DM_PELSWIDTH | Gdi.DM_PELSHEIGHT;

                // Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
                if (User.ChangeDisplaySettings(ref dmScreenSettings, User.CDS_FULLSCREEN) != User.DISP_CHANGE_SUCCESSFUL)
                {
                    // If The Mode Fails, Offer Two Options.  Quit Or Use Windowed Mode.
                    if (MessageBox.Show("The Requested Fullscreen Mode Is Not Supported By\nYour Video Card.  Use Windowed Mode Instead?", "NeHe GL",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == DialogResult.Yes)
                    {
                        fullscreen = false;                                     // Windowed Mode Selected.  Fullscreen = false
                    }
                    else
                    {
                        // Pop up A Message Box Lessing User Know The Program Is Closing.
                        MessageBox.Show("Program Will Now Close.", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                        return false;                                           // Return false
                    }
                }
            }

            if (fullscreen)
            {                                                    // Are We Still In Fullscreen Mode?
                FormBorderStyle = FormBorderStyle.None;                    // No Border
                Cursor.Hide();                                                  // Hide Mouse Pointer
            }
            else
            {                                                              // If Windowed
                FormBorderStyle = FormBorderStyle.Sizable;                 // Sizable
                Cursor.Show();                                                  // Show Mouse Pointer
            }

            Width = width;                                                 // Set Window Width
            Height = height + 40;                                               // Set Window Height

            Gdi.PIXELFORMATDESCRIPTOR pfd = new Gdi.PIXELFORMATDESCRIPTOR();    // pfd Tells Windows How We Want Things To Be
            pfd.nSize = (short)Marshal.SizeOf(pfd);                            // Size Of This Pixel Format Descriptor
            pfd.nVersion = 1;                                                   // Version Number
            pfd.dwFlags = Gdi.PFD_DRAW_TO_WINDOW |                              // Format Must Support Window
                Gdi.PFD_SUPPORT_OPENGL |                                        // Format Must Support OpenGL
                Gdi.PFD_DOUBLEBUFFER;                                           // Format Must Support Double Buffering
            pfd.iPixelType = (byte)Gdi.PFD_TYPE_RGBA;                          // Request An RGBA Format
            pfd.cColorBits = (byte)bitsPerPixel;                                       // Select Our Color Depth
            pfd.cRedBits = 0;                                                   // Color Bits Ignored
            pfd.cRedShift = 0;
            pfd.cGreenBits = 0;
            pfd.cGreenShift = 0;
            pfd.cBlueBits = 0;
            pfd.cBlueShift = 0;
            pfd.cAlphaBits = 0;                                                 // No Alpha Buffer
            pfd.cAlphaShift = 0;                                                // Shift Bit Ignored
            pfd.cAccumBits = 0;                                                 // No Accumulation Buffer
            pfd.cAccumRedBits = 0;                                              // Accumulation Bits Ignored
            pfd.cAccumGreenBits = 0;
            pfd.cAccumBlueBits = 0;
            pfd.cAccumAlphaBits = 0;
            pfd.cDepthBits = 32;                                                // 16Bit Z-Buffer (Depth Buffer)
            pfd.cStencilBits = 0;                                               // No Stencil Buffer
            pfd.cAuxBuffers = 0;                                                // No Auxiliary Buffer
            pfd.iLayerType = (byte)Gdi.PFD_MAIN_PLANE;                         // Main Drawing Layer
            pfd.bReserved = 0;                                                  // Reserved
            pfd.dwLayerMask = 0;                                                // Layer Masks Ignored
            pfd.dwVisibleMask = 0;
            pfd.dwDamageMask = 0;

            hDC = User.GetDC(Handle);                                      // Attempt To Get A Device Context
            if (hDC == IntPtr.Zero)
            {                                            // Did We Get A Device Context?
                KillGLWindow();                                                 // Reset The Display
                MessageBox.Show("Can't Create A GL Device Context.", "ERROR",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            pixelFormat = Gdi.ChoosePixelFormat(hDC, ref pfd);                  // Attempt To Find An Appropriate Pixel Format
            if (pixelFormat == 0)
            {                                              // Did Windows Find A Matching Pixel Format?
                KillGLWindow();                                                 // Reset The Display
                MessageBox.Show("Can't Find A Suitable PixelFormat.", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            if (!Gdi.SetPixelFormat(hDC, pixelFormat, ref pfd))
            {
                // Are We Able To Set The Pixel Format?
                KillGLWindow();                                                 // Reset The Display
                MessageBox.Show("Can't Set The PixelFormat.", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            hRC = Wgl.wglCreateContext(hDC);                                    // Attempt To Get The Rendering Context
            if (hRC == IntPtr.Zero)
            {                                            // Are We Able To Get A Rendering Context?
                KillGLWindow();                                                 // Reset The Display
                MessageBox.Show("Can't Create A GL Rendering Context.", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            if (!Wgl.wglMakeCurrent(hDC, hRC))
            {
                // Try To Activate The Rendering Context
                KillGLWindow();                                                 // Reset The Display
                MessageBox.Show("Can't Activate The GL Rendering Context.", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            Show();                                                        // Show The Window
            TopMost = true;                                                // Topmost Window
            Focus();                                                       // Focus The Window

            if (fullscreen)
            {                                                    // This Shouldn't Be Necessary, But Is
                Cursor.Hide();
            }

            return true;                                                        // Success
        }

        internal void KillGLWindow()
        {
            if (fullscreen)
            {                                                    // Are We In Fullscreen Mode?
                User.ChangeDisplaySettings(IntPtr.Zero, 0);                     // If So, Switch Back To The Desktop
                Cursor.Show();                                                  // Show Mouse Pointer
            }

            if (hRC != IntPtr.Zero)
            {                                            // Do We Have A Rendering Context?
                if (!Wgl.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero))
                {             // Are We Able To Release The DC and RC Contexts?
                    MessageBox.Show("Release Of DC And RC Failed.", "SHUTDOWN ERROR",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

                if (!Wgl.wglDeleteContext(hRC))
                {                                // Are We Able To Delete The RC?
                    MessageBox.Show("Release Rendering Context Failed.", "SHUTDOWN ERROR",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

                hRC = IntPtr.Zero;                                              // Set RC To Null
            }

            if (hDC != IntPtr.Zero)
            {                                            // Do We Have A Device Context?
                if (!IsDisposed)
                {                          // Do We Have A Window?
                    if (Handle != IntPtr.Zero)
                    {                            // Do We Have A Window Handle?
                        if (!User.ReleaseDC(Handle, hDC))
                        {                 // Are We Able To Release The DC?
                            MessageBox.Show("Release Device Context Failed.", "SHUTDOWN ERROR",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                    }
                }

                hDC = IntPtr.Zero;                                              // Set DC To Null
            }

            Hide();                                                    // Hide The Window
            Close();                                                   // Close The Form
        }

        public override void display_pmap(Graphics displayGraphics)
        {
            if (hDC != IntPtr.Zero)
            {
                Gdi.SwapBuffers(hDC);
            }
        }
    };

    public class GuiHalSurfaceWindowsFormsOpenGLBackend : GuiHalSurfaceWindowsFormsBase
    {
        public GuiHalSurfaceWindowsFormsOpenGLBackend(ImageFormats format)
            : base(format)
        {
            windowsFormsWindow = new WindowsFormBridgeOpenGLBackend(this, format);
        }

        public override void OnResize(int sx, int sy)
        {
            Bounds = new rect_d(0, 0, sx, sy); ;

            Gl.glViewport(0, 0, (int)Width, (int)Height);					// Reset The Current Viewport


            // The following lines set the screen up for a perspective view. Meaning things in the distance get smaller. 
            // This creates a realistic looking scene. 
            // The perspective is calculated with a 45 degree viewing angle based on the windows width and height. 
            // The 0.1f, 100.0f is the starting point and ending point for how deep we can draw into the screen.

            Gl.glMatrixMode(Gl.GL_PROJECTION);						// Select The Projection Matrix
            Gl.glLoadIdentity();							// Reset The Projection Matrix-

            Gl.glMatrixMode(Gl.GL_MODELVIEW);						// Select The Modelview Matrix
            Gl.glLoadIdentity();
            Gl.glScissor(0, 0, (int)Width, (int)Height);

            NewGraphics2D().Clear(new RGBA_Doubles(1, 1, 1, 1));
        }

        public override Graphics2D NewGraphics2D()
        {
            Graphics2D graphics2D;

            graphics2D = new Graphics2DOpenGL();

            graphics2D.PushTransform();
            return graphics2D;
        }

        public override void Init(int Width, int Height, GuiHalSurface.CreateFlags flags, GuiHalSurface.PixelFormat pixelFormat)
        {
            if (windowsFormsWindow.m_sys_format == GuiHalSurface.ImageFormats.pix_format_undefined)
            {
                throw new InvalidDataException();
            }

            m_window_flags = flags;

            System.Drawing.Size clientSize = new System.Drawing.Size();
            clientSize.Width = Width;
            clientSize.Height = Height;
            windowsFormsWindow.ClientSize = clientSize;

            if ((m_window_flags & CreateFlags.Resizable) == 0)
            {
                windowsFormsWindow.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
                windowsFormsWindow.MaximizeBox = false;
            }

            initialWidth = Width;
            initialHeight = Height;

            ((WindowsFormBridgeOpenGLBackend)windowsFormsWindow).CreateGLWindow((int)initialWidth, (int)initialHeight, 32, false);

            clientSize.Width = Width;
            clientSize.Height = Height;
            windowsFormsWindow.ClientSize = clientSize;

            Bounds = new rect_d(0, 0, Width, Height);

            OnInitialize();
            windowsFormsWindow.m_WindowContentNeedsRedraw = true;
        }

        public override void OnInitialize()
        {
            NewGraphics2D().Clear(new RGBA_Doubles(1, 1, 1, 1));

            base.OnInitialize();
        }

        public override void Run()
        {
            while (windowsFormsWindow.Created)
            {
                Application.DoEvents();
                OnIdle();
                if (MilliSecondsToSleepEachIdle > 0)
                {
                    System.Threading.Thread.Sleep(MilliSecondsToSleepEachIdle);
                }
            }

            ((WindowsFormBridgeOpenGLBackend)windowsFormsWindow).KillGLWindow();
        }
    };
}